import "./style.css";
import { ManhattanRouter } from "./manhatten";
import { Rectangle, Point, RouterDirection, RouterDirectionType } from "./types";

export interface Connection {
  fromRect: string;
  toRect: string;
  fromAnchor: string;
  toAnchor: string;
  start: Point;
  end: Point;
  path: string;
}

interface DraggingState {
  type: string;
  x: number;
  y: number;
  ref?: any;
  data?: Record<string, any>;
}

type Ref = any;

const nodeLayer: HTMLElement = document.getElementById("node-layer")!;
const connectionLayer: HTMLElement = document.getElementById("connection-layer")!;
const refs: Record<string, Ref> = {};
const dragging: DraggingState = {
  type: "",
  x: 0,
  y: 0,
};
const connections: Record<string, Connection[]> = {};
const selection: Record<string, Ref> = {};

function uuid(): string {
  return crypto.randomUUID();
}

function addConnection(rect: string, conn: Connection) {
  const l = connections[rect];
  if (l) {
    l.push(conn);
  } else {
    connections[rect] = [conn];
  }
}

document.addEventListener("dblclick", (e) => {
  nodeLayer.append(createRect(e));
});

document.addEventListener("click", (e) => {
  const el = e.target as Element;
  Object.values(selection).forEach((el) => {
    (el as HTMLElement).classList.remove("active");
  });
  Object.assign(selection, {});
  if (el.classList.contains("rect")) {
    el.classList.add("active");
    selection[(el as HTMLElement).dataset.id!] = el;
  }
});

document.addEventListener("mousedown", (e) => {
  const el = e.target as HTMLElement;
  const { clientX, clientY } = e;
  dragging.x = clientX;
  dragging.y = clientY;
  if (el.dataset.id) {
    dragging.type = el.nodeName == "DIV" ? "move" : "anchor";
    if (dragging.type == "anchor") {
      const pos = getAnchorPoint(el.parentElement!.getBoundingClientRect(), el.className as Anchor);
      dragging.x = pos.x;
      dragging.y = pos.y;
      dragging.data = {};
      dragging.data["fromAnchor"] = el.className;
      dragging.data["fromRect"] = el.parentElement?.dataset.id;
    } else {
      dragging.ref = refs[el.dataset.id];
    }
  } else {
    dragging.type = "select";
  }
  console.log("drag type", dragging.type);
});

document.addEventListener("mouseup", (e) => {
  console.log(e.target, dragging.type);
  try {
    if (dragging.type == "select") {
      const id = dragging.ref?.dataset.id;
      if (id) {
        delete refs[id];
      }
      dragging.ref?.remove();
    }
    if (dragging.type == "anchor") {
      const el = e.target as HTMLElement;
      if (el.nodeName == "I") {
        const start = { x: dragging.x, y: dragging.y };
        const end = getAnchorPoint(
          el.parentElement!.getBoundingClientRect(),
          el.className as Anchor,
        );
        const fromAnchor = dragging.data!["fromAnchor"];
        const fromRect = dragging.data!["fromRect"];
        const toRect = el.parentElement?.dataset.id;
        const connection = {
          fromRect,
          toRect,
          fromAnchor,
          toAnchor: el.className,
          start,
          end,
          path: dragging.ref!.dataset.id,
        } as Connection;
        addConnection(fromRect, connection);
        addConnection(toRect!, connection);

        const polyline = createSVGPolyline(connection);
        polyline.setAttribute("pointer-events", "none");
        const polyline1 = createSVGPolyline(connection);
        polyline1.setAttribute("stroke-width", "10");
        polyline1.setAttribute("stroke", "transparent");
        polyline1.setAttribute("cursor", "pointer");
        polyline.dataset.id = dragging.ref!.dataset.id;
        polyline1.dataset.id = polyline.dataset.id + "-shadow";

        connectionLayer.appendChild(polyline1);
        connectionLayer.appendChild(polyline);

        refs[dragging.ref!.dataset.id] = polyline;
        refs[dragging.ref!.dataset.id + "-shadow"] = polyline1;
        dragging.ref!.remove();
        return;
      }
      const id = dragging.ref?.dataset.id;
      if (id) {
        delete refs[id];
      }
      dragging.ref?.remove();
    }
  } finally {
    dragging.ref = undefined;
    dragging.type = "";
  }
});

document.addEventListener("mousemove", (e) => {
  const { clientX, clientY } = e;
  if (dragging.type == "select") {
    if (!dragging.ref) {
      const div = document.createElement("div");
      div.className = "select-box";
      div.style.top = `${dragging.y}px`;
      div.style.left = `${dragging.x}px`;
      dragging.ref = div;
      nodeLayer.appendChild(div);
    }
    const rect = dragging.ref;
    const [deltaX, deltaY] = [clientX - dragging.x, clientY - dragging.y];
    if (deltaX > 0 && deltaY < 0) {
      rect.style.top = `${clientY}px`;
    } else if (deltaX < 0 && deltaY > 0) {
      rect.style.left = `${clientX}px`;
    } else if (deltaX < 0 && deltaY < 0) {
      rect.style.top = `${clientY}px`;
      rect.style.left = `${clientX}px`;
    }
    rect.style.width = `${Math.abs(deltaX)}px`;
    rect.style.height = `${Math.abs(deltaY)}px`;
  } else {
    if (dragging.type == "move") {
      const rect = dragging.ref;
      const { top, left, width, height } = (rect as HTMLElement).getBoundingClientRect();
      const deltaX = clientX - dragging.x;
      const deltaY = clientY - dragging.y;

      rect!.style.top = `${top + height / 2 + deltaY}px`;
      rect!.style.left = `${left + width / 2 + deltaX}px`;
      rect!.title = `${top + deltaX},${left + deltaY}`;
      dragging.x += deltaX;
      dragging.y += deltaY;

      connections[rect.dataset.id]?.forEach((conn) => {
        console.log(conn);
        const path = refs[conn.path];
        if (conn.fromRect == rect.dataset.id) {
          const start = getAnchorPoint(
            (rect as HTMLElement).getBoundingClientRect(),
            conn.fromAnchor as Anchor,
          );
          conn.start = start;
          path.setAttribute("d", polylinePath(conn));
        } else if (conn.toRect == rect.dataset.id) {
          const end = getAnchorPoint(
            (rect as HTMLElement).getBoundingClientRect(),
            conn.toAnchor as Anchor,
          );
          conn.end = end;
          path.setAttribute("d", polylinePath(conn));
        }
      });
    } else if (dragging.type == "anchor") {
      const start = new Point(dragging.x, dragging.y);
      const end = new Point(e.clientX, e.clientY);
      if (dragging.ref) {
        dragging.ref!.setAttribute("d", linePath(start, end));
      } else {
        const path = createSVGPath(start, end);
        path.dataset.id = uuid();
        refs[path.dataset.id] = path;
        connectionLayer.appendChild(path);
        dragging.ref = path;
      }
    }
  }
});

const createRect = (e: MouseEvent) => {
  const { clientX, clientY } = e;
  const div = document.createElement("div");
  div.style.left = `${clientX}px`;
  div.style.top = `${clientY}px`;
  div.className = "rect";
  div.dataset.id = uuid();
  refs[div.dataset.id] = div;
  [
    "top-left",
    "top",
    "top-right",
    "left",
    "center",
    "right",
    "bottom-left",
    "bottom",
    "bottom-right",
  ].forEach((anchor) => {
    const i = document.createElement("i");
    i.className = anchor;
    i.dataset.id = uuid();
    div.appendChild(i);
  });
  return div;
};

function createSVGPath(start: Point, end: Point) {
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
  path.setAttribute("d", linePath(start, end));
  path.setAttribute("stroke", "black");
  path.setAttribute("fill", "none");
  return path;
}

function createSVGPolyline(conn: Connection) {
  const polyline = document.createElementNS("http://www.w3.org/2000/svg", "path");
  polyline.setAttribute("stroke", "black");
  polyline.setAttribute("fill", "none");
  polyline.setAttribute("d", polylinePath(conn));
  return polyline;
}

function linePath(start: Point, end: Point) {
  return `M${start.x} ${start.y} L${end.x} ${end.y}`;
}

type Anchor =
  | "top"
  | "top-left"
  | "top-right"
  | "left"
  | "center"
  | "right"
  | "bottom"
  | "bottom-left"
  | "bottom-right";

function getAnchorPoint(domRect: DOMRect, anchor: Anchor): Point {
  const rect = new Rectangle(domRect.x, domRect.y, domRect.width, domRect.height);
  switch (anchor) {
    case "top":
      return rect.getTopCenter();
    case "top-left":
      return rect.getTopLeft();
    case "top-right":
      return rect.getTopRight();
    case "left":
      return rect.getLeftMiddle();
    case "center":
      return rect.getCenter();
    case "right":
      return rect.getRightMiddle();
    case "bottom":
      return rect.getBottomCenter();
    case "bottom-left":
      return rect.getBottomLeft();
    case "bottom-right":
      return rect.getBottomRight();
  }
}

function polylinePath(conn: Connection): string {
  const { fromRect, toRect, fromAnchor, toAnchor, start, end } = conn;
  // const sourceRect = Rectangle.create((refs[fromRect] as HTMLElement).getBoundingClientRect());
  // const targetRect = Rectangle.create((refs[toRect] as HTMLElement).getBoundingClientRect());
  const sourceAnchor = getAnchorPoint(
    (refs[fromRect] as HTMLElement).getBoundingClientRect(),
    fromAnchor as Anchor,
  );
  const targetAnchor = getAnchorPoint(
    (refs[toRect] as HTMLElement).getBoundingClientRect(),
    toAnchor as Anchor,
  );
  let fromDir: RouterDirectionType = getDirection(start, end, fromAnchor);
  let toDir: RouterDirectionType = getDirection(end, start, toAnchor);

  const manhattan = new ManhattanRouter();

  // const path = orth(sourceRect, targetRect, sourceAnchor, targetAnchor, {});
  // path.unshift(sourceAnchor);
  // path.push(targetAnchor);
  const path: Point[] = [];
  manhattan.route(path, sourceAnchor, fromDir, targetAnchor, toDir);
  const m = path.shift();
  return `M${m!.x} ${m!.y} ` + path.map((p) => `L${p.x} ${p.y}`).join(" ");
}

function getDirection(start: Point, end: Point, anchor: string): RouterDirectionType {
  switch (anchor) {
    case "top":
      return RouterDirection.Up;
    case "left":
      return RouterDirection.Left;
    case "right":
      return RouterDirection.Right;
    case "bottom":
      return RouterDirection.Down;
    case "top-right":
      if (end.x > start.x) {
        return RouterDirection.Right;
      } else if (end.x < start.x) {
        return RouterDirection.Up;
      } else if (end.y < start.y) {
        return RouterDirection.Right;
      } else {
        return RouterDirection.Up;
      }
    case "top-left":
      if (end.x > start.x) {
        return RouterDirection.Up;
      } else if (end.x < start.x) {
        return RouterDirection.Left;
      } else if (end.y < start.y) {
        return RouterDirection.Left;
      } else {
        return RouterDirection.Up;
      }
    case "bottom-right":
      if (end.x > start.x) {
        return RouterDirection.Right;
      } else if (end.x < start.x) {
        return RouterDirection.Down;
      } else if (end.y < start.y) {
        return RouterDirection.Right;
      } else {
        return RouterDirection.Down;
      }
    case "bottom-left":
      if (end.x > start.x) {
        return RouterDirection.Down;
      } else if (end.x < start.x) {
        return RouterDirection.Left;
      } else if (end.y < start.y) {
        return RouterDirection.Left;
      } else {
        return RouterDirection.Down;
      }
    default:
      if (end.x > start.x) {
        return RouterDirection.Right;
      } else if (end.x < start.x) {
        return RouterDirection.Left;
      } else if (end.y < start.y) {
        return RouterDirection.Up;
      } else {
        return RouterDirection.Down;
      }
  }
}
